package postgres

import (
	"context"
	"fmt"
	"quipus/internal/database/model"
	"quipus/internal/utils"

	apb "quipus/proto/gen/assist"
	cpb "quipus/proto/gen/common"

	_ "github.com/jackc/pgx/v5/stdlib"
	_ "github.com/joho/godotenv/autoload"
	"github.com/pkg/errors"
	"google.golang.org/protobuf/encoding/protojson"
	"gorm.io/datatypes"
	"gorm.io/gen"
)

func (p *PGDB) CreateKnot(ctx context.Context, create *model.Knot) error {
	if !utils.UIDMatcher.MatchString(create.UID) {
		return errors.New("invalid uid")
	}

	p.query()
	qdo := q.WithContext(ctx)
	err := qdo.Knot.Create(create)
	if err != nil {
		return errors.Wrap(err, "failed to create knot")
	}

	return nil
}

func (p *PGDB) UpdateKnot(ctx context.Context, update *apb.UpdateKnot) error {
	p.query()
	Knot := q.WithContext(ctx).Knot
	Knot = Knot.Where(q.Knot.ID.Eq(update.Id))
	if len(update.Uid) > 0 {
		Knot = Knot.Where(q.Knot.UID.Eq(update.Uid))
	}

	updateValue := &model.Knot{}
	if len(update.Content) > 0 {
		updateValue.Content = update.Content
	}
	if update.Payload != nil {
		p, e := protojson.Marshal(update.Payload)
		if e != nil {
			return errors.Wrap(e, "failed to marshal payload")
		}
		updateValue.Payload = string(p)
	}
	if update.Visibility != cpb.Visibility_VISIBILITY_UNSPECIFIED {
		updateValue.Visibility = update.Visibility.String()
	}
	if update.RowStatus != cpb.RowStatus_ROW_STATUS_UNSPECIFIED {
		updateValue.RowStatus = update.RowStatus.String()
	}
	if update.CreatedTs != nil {
		updateValue.CreatedTs = update.CreatedTs.GetSeconds()
	}
	if update.UpdatedTs != nil {
		updateValue.UpdatedTs = update.UpdatedTs.GetSeconds()
	}

	_, err := Knot.Updates(updateValue)
	if err != nil {
		return errors.Wrap(err, "failed to update knot")
	}
	return nil
}

func (p *PGDB) DeleteKnot(ctx context.Context, delete *apb.DeleteKnot) error {
	p.query()
	Knot := q.WithContext(ctx).Knot
	if delete.Id > 0 {
		Knot = Knot.Where(q.Knot.ID.Eq(delete.Id))
	}

	if delete.CreatorId > 0 {
		Knot = Knot.Where(q.Knot.CreatorID.Eq(delete.CreatorId))
	}
	_, err := Knot.Delete()
	if err != nil {
		return errors.Wrap(err, "failed to delete knot")
	}
	return nil
}

func (p *PGDB) ListKnots(ctx context.Context, find *apb.FindKnot) ([]*model.Knot, int32, error) {
	p.query()
	qdo := q.WithContext(ctx)
	knot := qdo.Knot

	if find.Id > 0 {
		knot = knot.Where(q.Knot.ID.Eq(find.Id))
	}

	visibilityConditions := []gen.Condition{}
	visibilityConditions = append(visibilityConditions,
		q.Knot.Visibility.Eq(cpb.Visibility_name[int32(cpb.Visibility_PUBLIC)]))
	if find.CurrentUserId > 0 {
		visibilityConditions = append(visibilityConditions,
			q.Knot.CreatorID.Eq(find.CurrentUserId))
	}

	if find.Visibilities != nil {
		for _, v := range find.Visibilities {
			visibilityConditions = append(visibilityConditions, q.Knot.Visibility.Eq(cpb.Visibility_name[int32(v)]))
		}
	}

	if find.Uid != "" {
		knot = knot.Where(q.Knot.UID.Eq(find.Uid))
	}

	if find.RowStatus != 0 {
		status := cpb.RowStatus_name[int32(find.RowStatus)]
		knot = knot.Where(q.Knot.RowStatus.Eq(status))
	}

	if find.CreatorId > 0 {
		// knot = knot.Where(q.Knot.CreatorID.Eq(find.CreatorId))
		if find.CurrentUserRole == cpb.Role_ADMIN {
			knot = knot.Where(q.Knot.CreatorID.Eq(find.CreatorId))
		} else {
			if find.CurrentUserId != find.CreatorId {
				knot = knot.Where(
					q.Knot.CreatorID.Eq(find.CreatorId),
					q.Knot.Visibility.Eq(cpb.Visibility_name[int32(cpb.Visibility_PUBLIC)]),
				)
			} else {
				knot = knot.Where(q.Knot.CreatorID.Eq(find.CreatorId))
			}
		}
	} else {
		kv := qdo.Knot

		if len(visibilityConditions) > 0 {
			for _, cond := range visibilityConditions {
				kv = kv.Or(cond)
			}
		}

		knot = knot.Where(kv)
	}

	if find.CreateTime != nil {
		knot = knot.Where(q.Knot.CreatedTs.Eq(find.CreateTime.GetSeconds()))
	}

	if find.CreatedTsAfter != nil {
		knot = knot.Where(q.Knot.CreatedTs.Gt(find.CreatedTsAfter.GetSeconds()))
	}

	if find.CreatedTsBefore != nil {
		knot = knot.Where(q.Knot.CreatedTs.Lt(find.CreatedTsBefore.GetSeconds()))
	}

	if find.UpdatedTsAfter != nil {
		knot = knot.Where(q.Knot.UpdatedTs.Gt(find.UpdatedTsAfter.GetSeconds()))
	}

	if find.UpdatedTsBefore != nil {
		knot = knot.Where(q.Knot.UpdatedTs.Lt(find.UpdatedTsBefore.GetSeconds()))
	}

	if find.KeyWords != nil {
		for _, kw := range find.KeyWords {
			knot = knot.Where(q.Knot.Content.Like(fmt.Sprintf("%%%s%%", kw)))
		}
	}

	if find.Payload != nil {
		v := find.Payload
		if v.Raw != "" {
			knot = knot.Where(q.Knot.Payload.Eq(v.Raw))
		}

		jsonCondition := datatypes.JSONQuery("payload").HasKey("property")
		if len(v.TagSearch) != 0 {
			for _, tag := range v.TagSearch {
				knot = knot.Where(gen.Cond(jsonCondition.HasKey("tags").Equals(tag))...)
			}
		}

		if v.HasTaskList {
			knot = knot.Where(gen.Cond(jsonCondition.HasKey("has_link").Equals(true))...)
		}

		if v.HasCode {
			knot = knot.Where(gen.Cond(jsonCondition.HasKey("has_code").Equals(true))...)
		}

		if v.HasTaskList {
			knot = knot.Where(gen.Cond(jsonCondition.HasKey("has_task_list").Equals(true))...)
		}

		if v.HasIncompleteTasks {
			knot = knot.Where(gen.Cond(jsonCondition.HasKey("has_incomplete_tasks").Equals(true))...)
		}
	}

	knot = knot.LeftJoin(&model.KnotArrangement{}, q.Knot.ID.EqCol(q.KnotArrangement.KnotID), q.Knot.CreatorID.EqCol(q.KnotArrangement.UserID))
	knot = knot.LeftJoin(&model.KnotRelation{}, q.Knot.ID.EqCol(q.KnotRelation.KnotID))

	if find.ExcludeComments {
		knot = knot.Where(q.KnotRelation.RelatedKnotID.IsNull())
	}

	if find.ExcludeChildren {
		knot = knot.Where(q.Knot.ParentID.Eq(0))
	} else if find.ParentId > 0 {
		knot = knot.Where(q.Knot.ParentID.Eq(find.ParentId))
	}

	if find.OrderByPinned {
		knot = knot.Order(q.KnotArrangement.Pinned.Desc())
	}

	if find.OrderByTimeAsc {

		knot = knot.Order(q.Knot.ID.Asc())

		if find.OrderByUpdatedTs {
			knot = knot.Order(q.Knot.UpdatedTs.Asc())
		} else {
			knot = knot.Order(q.Knot.CreatedTs.Asc())
		}
	} else {
		knot = knot.Order(q.Knot.ID.Desc())

		if find.OrderByUpdatedTs {
			knot = knot.Order(q.Knot.UpdatedTs.Desc())
		} else {
			knot = knot.Order(q.Knot.CreatedTs.Desc())
		}
	}

	if find.Random {
		knot = knot.Order()
	}

	if find.SubjectId > 0 {
		knot = knot.Where(q.Knot.SubjectID.Eq(find.SubjectId))
	}

	knots, count, err := knot.FindByPage(int(find.Offset), int(find.Limit))
	if err != nil {
		return nil, 0, err
	}

	// fmt.Print(count)

	return knots, int32(count), err
}

func (p *PGDB) GetKnot(ctx context.Context, find *apb.FindKnot) (*model.Knot, error) {

	p.query()
	Knot := q.WithContext(ctx).Knot
	return Knot.Where(q.Knot.ID.Eq(find.Id)).First()
}

func (p *PGDB) CreateKnotRT(ctx context.Context, create *model.KnotRecommendTopic) error {
	p.query()
	Knot := q.WithContext(ctx).KnotRecommendTopic
	return Knot.Create(create)
}
func (p *PGDB) DeleteKnotRT(ctx context.Context, knotId int32) error {
	p.query()
	Knot := q.WithContext(ctx).KnotRecommendTopic
	_, err := Knot.Where(q.KnotRecommendTopic.ID.Eq(knotId)).Delete()
	return err
}

func (p *PGDB) GetKnotRT(ctx context.Context, knotId int32) (*model.KnotRecommendTopic, error) {
	p.query()
	Knot := q.WithContext(ctx).KnotRecommendTopic
	return Knot.Where(q.KnotRecommendTopic.ID.Eq(knotId)).First()
}
